/**
 * Created by xinxinran on 2017/8/22.
 */
'use strict';
const PROTOCOL_STATE_NOT_BEGIN = 0,
    PROTOCOL_STATE_CHECK_HEADER = 1,
    PROTOCOL_STATE_GET_LENGTH = 2,
    PROTOCOL_STATE_GET_BODY = 3,
    PROTOCOL_STATE_FINISHED = 4,

    PROTOCOL_MAX_BODY_LENGTH = 1024 * 1024 * 10,

    loggerUtil = require('../utils/loggerUtil');


class SocketReader{
    constructor(){
        this.totalLen = 4096;
        this.bufferdData = new Buffer(this.totalLen);
        this.pointer = 0;
        this.contentLen = 0;
        this.remainLen = this.totalLen;
    }

    dataConcat(comeBuffer){
        let comeLen, notProcessLen, preCopyLen, tmpNewBuffer;
        if(!comeBuffer){
            return;
        }
        if(comeBuffer){
            comeLen = comeBuffer.length;
            if(this.remainLen < comeLen){
                notProcessLen = this.contentLen - this.pointer;
                preCopyLen = comeLen  + notProcessLen;
                if(preCopyLen > this.totalLen){
                    this.totalLen = (preCopyLen * 1.5) | 0;
                    tmpNewBuffer = new Buffer(this.totalLen);
                    notProcessLen && this.bufferdData.copy(tmpNewBuffer, 0, this.pointer, this.contentLen);
                    comeBuffer.copy(tmpNewBuffer, notProcessLen);

                    this.bufferdData = tmpNewBuffer;
                }else{
                    tmpNewBuffer = new Buffer(preCopyLen);
                    notProcessLen && this.bufferdData.copy(tmpNewBuffer, 0, this.pointer, this.contentLen);
                    comeBuffer.copy(tmpNewBuffer, notProcessLen);

                    tmpNewBuffer.copy(this.bufferdData, 0);
                    tmpNewBuffer = null;
                }

                this.pointer = 0;
                this.contentLen = preCopyLen;
                this.remainLen = this.totalLen - preCopyLen;
            }else{
                comeBuffer.copy(this.bufferdData, this.contentLen);
                this.remainLen -= comeLen;
                this.contentLen += comeLen;
            }
        }
    }

    readString(len){
        let startPos = this.pointer, notProcessLen = this.contentLen - this.pointer;
        if(len > 0){
            if(notProcessLen <= 0 || len > notProcessLen){
                return false;
            }
            this.pointer +=len;
            return this.bufferdData.toString('utf-8', startPos, this.pointer);
        }
        return '';
    }

    readByte(){
        if(this.contentLen > this.pointer){
            return this.bufferdData.readInt8(this.pointer++);
        }else{
            return false;
        }
    }

    readShort(){
        let notProcessLen = this.contentLen - this.pointer, result;
        if(notProcessLen > 3){
            result =  this.bufferdData.readInt32LE(this.pointer);
            this.pointer += 4;
            return result;
        }
        return false;
    }

    readInt(){
        let notProcessLen = this.contentLen - this.pointer, result;
        if(notProcessLen > 3){
            result =  this.bufferdData.readInt32LE(this.pointer);
            this.pointer += 4;
            return result;
        }
        return false;
    }

    readBuffer(len){
        let startPos = this.pointer, notProcessLen = this.contentLen - this.pointer;
        if(len > 0){
            if(notProcessLen <= 0 || len > notProcessLen){
                return false;
            }
            this.pointer += len;
            return this.bufferdData.slice(startPos, this.pointer);
        }
        return null;
    }
}

class ProtocolBean{
    constructor(){
        this.renewProperties();
    }

    renewProperties(){
        this.totalLen = 0;
        this.type = -1;
        this.processState = PROTOCOL_STATE_NOT_BEGIN;
        this.bodyLen = 0;
        this.pathLen = 0;
        this.path = null;
        this.realPath = null;
        this.renterCode = null;
        this.content = null;
        this.bodyBuffer = null;
    }

    getContent(){
        let pLen;

        switch(this.type){
            case 1 :
            case 2 :
            case 3 :
                if(this.bodyBuffer && this.bodyBuffer.length){
                    return false;
                }
                break;
            case 4 :
                if(this.bodyBuffer instanceof Buffer) {
                    this.content = this.bodyBuffer.toString('utf-8', 0, this.bodyLen);
                }else{
                    return false;
                }
                break;
            case 5 :
                if(this.bodyBuffer){
                    pLen = this.bodyBuffer.readInt16LE(0);
                    if(pLen < 0 || pLen > this.bodyBuffer.length - 3){
                        return false;
                    }else{
                        this.pathLen = pLen;
                        this.path = this.bodyBuffer.toString('utf-8', 2, pLen + 2);

                        this.content = this.bodyBuffer.toString('utf-8', pLen + 2, this.bodyLen);
                    }
                }else{
                    return false;
                }
                break;
            case 7 :
                if(this.bodyBuffer){
                    pLen = this.bodyBuffer.readInt16LE(0);
                    if(pLen < 0 || pLen > this.bodyBuffer.length - 3){
                        return false;
                    }else{
                        this.pathLen = pLen;
                        this.path = this.bodyBuffer.toString('utf-8', 2, pLen + 2);

                        this.content = this.bodyBuffer.slice(pLen + 2, this.bodyLen);
                    }
                }else{
                    return false;
                }
                break;
            default :
                return false;
        }

        return true;
    }

    processPath(){
        if(this.path){
            this.realPath = this.path;
            this.renterCode = '';
        }
    }

    // noinspection JSMethodCanBeStatic
    genPackageBuffer(type, path, content){
        let pBuffer, tLen, pathBuffer, pathBufferLen, contentBuffer;
        switch(type){
            case 1 :
            case 2 :
            case 3 :
                tLen = 7;
                pBuffer = new Buffer(tLen);
                pBuffer.write('md', 'utf-8');
                pBuffer.writeInt32LE(tLen, 2);
                pBuffer.writeInt8(type, 6);
                break;
            case 4 :
                content = content || '{}';
                if(typeof content !== 'string'){
                    try{content = JSON.stringify(content);}catch(e){content = '{"msg" : "invalid response"}'}
                }
                contentBuffer = new Buffer(content, 'utf-8');
                tLen = 7 + contentBuffer.length;

                pBuffer = new Buffer(tLen);
                pBuffer.write('md', 'utf-8');
                pBuffer.writeInt32LE(tLen, 2);
                pBuffer.writeInt8(type, 6);
                contentBuffer.copy(pBuffer, 7);
                contentBuffer = null;
                break;
            case 5 :
                path = path || '';
                pathBuffer = new Buffer(path, 'utf-8');
                pathBufferLen = pathBuffer.length;
                content = content || '{}';
                if(typeof content !== 'string'){
                    try{content = JSON.stringify(content);}catch(e){content = '{"msg" : "invalid response"}'}
                }
                contentBuffer = new Buffer(content, 'utf-8');
                tLen = 9 + contentBuffer.length + pathBufferLen;

                pBuffer = new Buffer(tLen);
                pBuffer.write('md', 'utf-8');
                pBuffer.writeInt32LE(tLen, 2);
                pBuffer.writeInt8(type, 6);
                pBuffer.writeInt16LE(pathBufferLen, 7);
                pathBuffer.copy(pBuffer, 9);
                contentBuffer.copy(pBuffer, 9 + pathBufferLen);
        }
        return pBuffer;
    }
}

const genRequestFunc = Symbol('_genRequestObj'),
    genResponseFunc = Symbol('_genResponseObj');
class Protocol{
    constructor(client, isClient){
        this.socketOper = client;
        this.reader = new SocketReader();
        this.receiveBean = new ProtocolBean();
        isClient || this.socketOper.send(this.receiveBean.genPackageBuffer(1));
        this.socketOper.protocol = this;
        this.isClient = isClient;

        this.logger = loggerUtil.getLogger();
    }

    receiveData(data){
        this.reader.dataConcat(data);
        this.process();
    }

    process(){
        let rBean = this.receiveBean, step = rBean.processState, header, packageLen, beanType, bodyBuffer;
        try{
            // noinspection FallThroughInSwitchStatementJS
            switch(step){
                case PROTOCOL_STATE_NOT_BEGIN :
                    header = this.reader.readString(2);
                    if(header === false){
                        break;
                    }
                    if(header === 'md'){
                        rBean.processState = PROTOCOL_STATE_CHECK_HEADER;
                    }else{
                        this.socketOper.end();
                        break;
                    }
                case PROTOCOL_STATE_CHECK_HEADER :
                    packageLen = this.reader.readInt();
                    if(packageLen === false){
                        break;
                    }
                    if(packageLen > 6 && packageLen < PROTOCOL_MAX_BODY_LENGTH){
                        rBean.totalLen = packageLen;
                        rBean.bodyLen = packageLen - 7;
                        rBean.processState = PROTOCOL_STATE_GET_LENGTH;
                    }else{
                        this.socketOper.end();
                        break;
                    }
                case PROTOCOL_STATE_GET_LENGTH :
                    beanType = this.reader.readByte();
                    if(beanType === false){
                        break;
                    }
                    if(beanType >0 || beanType < 6){
                        rBean.type = beanType;
                        rBean.processState = PROTOCOL_STATE_GET_BODY;
                    }else{
                        this.socketOper.end();
                        break;
                    }
                case PROTOCOL_STATE_GET_BODY :
                    if(rBean.bodyLen > 0){
                        bodyBuffer = this.reader.readBuffer(this.receiveBean.bodyLen);
                        if(bodyBuffer === false){
                            break;
                        }

                        rBean.bodyBuffer = bodyBuffer;

                        if(!rBean.getContent()){
                            this.socketOper.end();
                            break;
                        }
                    }
                    rBean.processState = PROTOCOL_STATE_FINISHED;
                case PROTOCOL_STATE_FINISHED :
                    this.doActCommand();
                    rBean.renewProperties();
            }
        }catch(e){
            this.logger.error('protocol process error', e);
            this.socketOper.end();
        }
    }

    doActCommand(){
        let rBean = this.receiveBean, callback, response, operator = this.socketOper;
        if(rBean.processState !== PROTOCOL_STATE_FINISHED){
            return;
        }

        switch(rBean.type){
            case 2 :
                operator.send(rBean.genPackageBuffer(3));
                //operator.doCommand('heartbeat');
                break;
            case 5:
            case 7:
                this.logger.debug('[socket]receive data... ', {totalLen : rBean.totalLen, type : rBean.type, bodyLen : rBean.bodyLen, path: rBean.path, content: typeof rBean.content === 'string' ? rBean.content : '[Buffer]'});
                if(rBean.path.charAt(0) === '#'){
                    operator.doCommand(rBean.path.substring(1), rBean.type === 5 ? JSON.parse(rBean.content) : rBean.content);
                }else{
                    response = this[genResponseFunc]();
                    callback = function(){
                        response._flush();
                    };
                    callback.err = function(msg){
                        operator.send(rBean.genPackageBuffer(4, null, {code : -1, msg: msg}));
                    };
                    operator.dispatchRequest(this[genRequestFunc](), response, callback);
                }
                break;
            case 4:
                this.logger.debug('[socket]receive data... ', {totalLen : rBean.totalLen, type : rBean.type, bodyLen : rBean.bodyLen, path: rBean.path, content: rBean.content});
                if(typeof operator['doResolve'] === 'function'){  // 接收到4类数据，除非为广播消息（node发送到clusterManager），否则视为非法数据
                    operator['doResolve'](rBean.content);
                }else{
                    operator.end();
                }
                break;
            case 1:
            case 3:
                this.isClient || operator.end();
                break;
        }
    }

    [genRequestFunc](){
        let req = {}, rBean = this.receiveBean;
        try{
            if(typeof this.receiveBean.content === 'string'){
                req.params = JSON.parse(this.receiveBean.content);
            }else{
                req.params = {};
            }

        }catch(e){
            req.params = {};
        }

        req.url = rBean.path;
        req.body = rBean.content;
        rBean.processPath();
        req.rurl = rBean.realPath;

        req.contentType = () => 'socket/json';

        return req;
    }

    [genResponseFunc](){
        let res = {}, reqBuffers = [], rBean = this.receiveBean, socketOper = this.socketOper;
        res.send = function(content){
            let contObj = {};
            if(typeof content === 'string'){
                contObj.msg = content;
            }else if(typeof content === 'number'){
                contObj.code = content;
            }else if(typeof content === 'object' && content){
                Object.assign(contObj, content);
            }

            reqBuffers.push(rBean.genPackageBuffer(4, null, contObj));
        };

        res._flush = function(){
            reqBuffers.forEach(buffer => {
                socketOper.send(buffer);
            });
        };

        return res;
    }
}

module.exports = Protocol;
